package calao.storage;

import calao.util.Cursor;
import calao.util.Cursors;
import calao.util.Filter;

/** A data storage point.
 */
public abstract class Storage {

	/** Stores a record.
	 */
	public abstract void store(Record record);
	
	public final <T> void store(ObjectTable<T> table, T object)
	{
		store(table.binding.toRecord(object));
	}
	
	/** Lists tables currently used by this storage.
	 */
	public abstract Iterable<Table> listTables();
	
	/** Lists all records of a table.
	 */
	public Cursor<Record> list(Table table)
	{
		return listResults(new Search(table));
	}

	/** Lists all records of a table.
	 */
	public final <T> Cursor<T> listObjects(ObjectTable<T> table)
	{
		return table.transform(list((Table)table));
	}

	/** Lists records of a table.
	 */
	public Cursor<Record> list(Table table, long offset)
	{
		Cursor<Record> cursor = listResults(new Search(table));
		cursor.skipNext(offset);
		return cursor;
	}

	/** Lists records of a table.
	 */
	public final <T> Cursor<T> listObjects(ObjectTable<T> table, long offset)
	{
		return table.transform(list((Table)table, offset));
	}

	/** Gets records of a table.
	 */
	public Record[] getRecords(Table table)
	{
		return Cursors.toArray(list(table), Record.class);
	}

	/** Gets objects of a table.
	 */
	public <T> T[] getObjects(ObjectTable<T> table)
	{
		return Cursors.toArray(listObjects(table), table.binding.type);
	}

	/** Searches for records.
	 */
	public abstract Cursor<Record> listResults(Search search);
	
	/** Searches for records.
	 */
	public final <T> Cursor<T> listResults(ObjectSearch<T> search)
	{
		return search.getObjectTable().transform(listResults((Search)search));
	}
	
	/** Gets results of a search.
	 */
	public Record[] getResults(Search search)
	{
		return Cursors.toArray(listResults(search), Record.class);		
	}

	/** Gets results of a search.
	 */
	public <T> T[] getResults(ObjectSearch<T> search)
	{
		return Cursors.toArray(listResults(search), search.getObjectTable().binding.type);		
	}

	/** Gets results of a search.
	 */
	public <T> T[] getResults(ObjectSearch<T> search, Filter<T> filter)
	{
		return Cursors.toArray(listResults(search), search.getObjectTable().binding.type, filter);		
	}

	/** Fetches a record identified by its primary key.
	 */
	public Record fetch(Table table, Object... keyValues)
	{
		Cursor<Record> cursor = newPrimaryKeyCursor(table, keyValues);
		try {
			return cursor.next()? cursor.get() : null;
		}
		finally {
			cursor.close();
		}
	}

	/** Fetches a record identified by its primary key.
	 */
	public final <T> T fetchObject(ObjectTable<T> table, Object... keyValues)
	{
		Record rec = fetch((Table)table, keyValues);
		return rec!=null? table.binding.toObject(rec) : null;
	}

	/** Drops a record identified by its primary key.
	 * Records are forever lost and cannot be recovered.
	 */
	public void drop(Table table, Object... keyValues)
	{
		Cursor<Record> cursor = newPrimaryKeyCursor(table, keyValues);
		try {
			if (cursor.next()) cursor.delete();
		}
		finally {
			cursor.close();
		}
	}
		
	@SuppressWarnings("unchecked")
	private Cursor<Record> newPrimaryKeyCursor(Table table, Object... keyValues)
	{
		Search search = new Search(table);
		int count = 0;
		for (Field field : table.primaryKeyIndex.fields) {
			search.add(new EqualCondition(field, keyValues[count++]));
		}
		return listResults(search);		
	}

	/** Gets the next value in a sequence.
	 * This method increases the value of the target sequence by one and returns the
	 * new sequence value. This operation is atomic on the specified sequence.
	 */
	public synchronized int getNextValue(Sequence sequence)
	{
		int value = getValue(sequence);
		if (value==sequence.getMaxValue()) value = sequence.getMinValue();
		else value++;
		setValue(sequence, value);
		return value;
	}
	
	/** Drops all records of a table.
	 */
	public void clear(Table table)
	{
		Cursor<Record> cursor = listResults(new Search(table));
		try {
			while (cursor.next()) cursor.delete();
		}
		finally {
			cursor.close();
		}
	}

	/** Sets the value of a sequence.
	 */
	public abstract void setValue(Sequence sequence, int value);
	
	/** Gets the value of a sequence.
	 */
	public abstract int getValue(Sequence sequence);

	/** Closes this storage.
	 * Once closed a storage can no longer be used, and any further call to this method
	 * has no effect.
	 */
	public abstract void close();
	
	/** Counts records in a table.
	 */
	public long countRecords(Table table)
	{
		Cursor<Record> cursor = listResults(new Search(table));
		long count = 0;
		try {
			while (cursor.next()) count++;
			return count;
		}
		finally {
			cursor.close();
		}		
	}
	
	/** Clears this storage.
	 * This operation removes all storage data.
	 */
	public abstract void clear();
	
	/** Gets a numeric identifier for a data binding.
	 */
	public abstract int getNumericId(DataBinding binding);
	
	/** Gets the list of tables in this storage.
	 */
	public abstract Table[] getTables();
		
}
